package com.ims.equation.client.latex;

import java.util.List;
import java.util.Map;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;
import com.ims.equation.client.dialog.LoadDialog;

/**
 * Builder which creates the different {@link Widget}s for the LaTex-Editor and connect them together.
 * 
 * @author Marcus
 *
 */
public class LaTexEditorBuilder {

	public static final float STANDARD_SIZE = 20.0f;
	
	private Map<String, List<String>> supportedLaTexNotations;
	private LaTexEditorElements elements;
	private LaTexRenderServiceAsync asynchronLatexRenderer;
	private float size = STANDARD_SIZE;
	private boolean renderAfterAnyChange = false;
	private LoadDialog loadWindow;
	
	
	private LaTexEditorBuilder(){
		supportedLaTexNotations = Maps.newHashMap();
		elements = new LaTexEditorElements();
		asynchronLatexRenderer = LaTexRenderService.Util.getInstance();
			
	}
	
	private LaTexEditorBuilder(LaTexRenderServiceAsync pLaTexRenderer){
		supportedLaTexNotations = Maps.newHashMap();
		elements = new LaTexEditorElements();
		asynchronLatexRenderer = pLaTexRenderer;
	}
	
	public static LaTexEditorBuilder getBuilderWithRenderer(LaTexRenderServiceAsync pLaTexRenderer){
		return new LaTexEditorBuilder(pLaTexRenderer);
	}
	
	public static LaTexEditorBuilder getBuilder(){
		return new LaTexEditorBuilder();
	}
	
	public LaTexEditorBuilder setSize(float pSize){
		this.size = pSize;
		return this;
	}
	
	public LaTexEditorBuilder addNewLatexNotations(String key, String ... pNotations){
		if(supportedLaTexNotations.containsKey(key)){
			supportedLaTexNotations.get(key).addAll(Lists.newArrayList(pNotations));
		} else{
			List<String> newNotations = Lists.newArrayList(pNotations);
			supportedLaTexNotations.put(key, newNotations);
		}
		
		return this;
	}
	
	public LaTexEditorBuilder setTextArea(TextArea pTextArea){
		this.elements.inputField = pTextArea;
		return this;
	}
	
	public LaTexEditorBuilder setRenderButtonText(String pText){
		this.elements.getRenderButton().setText(pText);
		return this;
	}
	
	public LaTexEditorBuilder setRenderButton(Button pRenderButton){
		this.elements.renderButton = pRenderButton;
		return this;
	}
	
	public LaTexEditorBuilder setImage(Image pImage){
		this.elements.renderedImage = pImage;
		return this;
	}
	
	public LaTexEditorBuilder setRenderAfterChange(boolean pRenderAfterAnyChange){
		this.renderAfterAnyChange = pRenderAfterAnyChange;
		return this;
	}
	
	public LaTexEditorElements build(){
		
		for(String key : this.supportedLaTexNotations.keySet()){
			List<String> notations = this.supportedLaTexNotations.get(key);
			asynchronLatexRenderer.base64CodedLatexNotations(size, notations, getNewAsynchronButtonSetBuilder(key, elements));
		}
		
		waitUntilButtonsAreCreated();
		
		
	}

	private void waitUntilButtonsAreCreated() {
		if(loadWindow != null){
			loadWindow.startDialog();
		}
		while(this.elements.getNotationButtons().keySet().containsAll(this.supportedLaTexNotations.keySet())){
			int expectedSize = this.supportedLaTexNotations.keySet().size();
			int actualSize= this.elements.getNotationButtons().size();
			double percent = actualSize / expectedSize;
			
			loadWindow.addActualPercent(percent);
			
		}
		
		if(loadWindow != null){
			loadWindow.stopDialog();
		}
	}
	
	
	
	public AsyncCallback<Map<String, String>> getNewAsynchronButtonSetBuilder(final String setIdentifer, final LaTexEditorElements pContainer){
		return new AsyncCallback<Map<String,String>>() {

			@Override
			public void onFailure(Throwable caught) {
				throw new RuntimeException("Cannot render the passed LaTex notations.", caught);
			}

			@Override
			public void onSuccess(Map<String, String> result) {
				
				for(String key : result.keySet()){
					String html = result.get(key);
					Button newButton = new Button(html);
					newButton.addClickHandler(getNotationAdder(key, pContainer.inputField));
					pContainer.addNewButtonsTo(key, newButton);
				}
				
			}
		};
	}
	
	private ClickHandler getNotationAdder(final String pNotation, final TextArea pArea){
		return new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				String text = pArea.getText();
				int courserPos = pArea.getCursorPos();
				
				if(!pArea.getSelectedText().isEmpty()){
					text = text.replace(pArea.getSelectedText(), pNotation);
				} else {	
					if(courserPos >= text.length()){
						text = text + pNotation;
					} else{
						text = text.substring(0, courserPos) + pNotation + text.substring((courserPos + 1 ), (text.length() - 1));
					}
				}
				
				pArea.setText(text);
			}
		};
	}
	
	public class LaTexEditorElements{
		private TextArea inputField;
		private Image renderedImage;
		private Map<String, List<Button>> availableButtons;
		private Button renderButton;
		private Function<Boolean, String> renderFunction;
		private LaTexRenderServiceAsync asynchronLatexRenderer;
		
		public LaTexEditorElements(){
			inputField = new TextArea();
			renderedImage = new Image();
			availableButtons = Maps.newHashMap();
			renderButton = new Button("Render LaTex");
			asynchronLatexRenderer = LaTexRenderService.Util.getInstance();
		}
		
		public void setLaTexRenderer(LaTexRenderServiceAsync pRenderer){
			this.asynchronLatexRenderer = pRenderer;
		}
		
		public Function<String, Void> getImageRenderFunction(){
			return new Function<String, Void>() {

				@Override
				public Void apply(String arg0) {
					asynchronLatexRenderer.base64CodedLatexNotation(arg0, renderedImage.getOffsetWidth(), new AsyncCallback<String>() {

						@Override
						public void onFailure(Throwable caught) {
							throw new RuntimeException("cannot render the passed LaTex notation.");
						}

						@Override
						public void onSuccess(String result) {
							renderedImage.setUrl(result);
						}
					});
					
					return null;
				}
			};
		}
		
		public Button getRenderButton(){
			return this.renderButton;
		}
		
		public Image getRenderedImage(){
			return this.renderedImage;
		}
		
		public Map<String, List<Button>> getNotationButtons(){
			return this.availableButtons;
		}
		
		public void addNewButtonsTo(String key, Button...buttons){
			List<Button> newButtons = Lists.newArrayList(buttons);
			
			if(this.availableButtons.containsKey(key)){
				this.availableButtons.get(key).addAll(newButtons);
			} else{
				this.availableButtons.put(key, newButtons);
			}
		}
	}
}
